package compilers.AST.statement;

import java.io.PrintStream;

import compilers.AST.Node;
import compilers.AST.basics.Identifier;
import compilers.AST.expression.Expression;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableElement;
import compilers.symbolTable.SymbolTableScope;
import compilers.symbolTable.SymbolTableVariableElement;

public class VariableAssignmentStatement extends Statement {
	
	private Identifier fLValue;
	private Expression fRValue;
	
	public VariableAssignmentStatement() {	}
	
	public VariableAssignmentStatement(int lineNr) {
		super(lineNr);
	}
	
	public void print(int identLevel) {
		println(identLevel, "VariableAssignmentStatement");
		fLValue.print(identLevel+1);
		fRValue.print(identLevel+1);
	}
	
	public void setLValue(Identifier lValue) {
		fLValue = lValue;
	}
	
	public void setRValue(Expression rValue) {
		fRValue = rValue;
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fLValue.checkSemantics(currentScope);
		fRValue.checkSemantics(currentScope);
		
		// Check whether a is not an array
		SymbolTableElement temp = currentScope.getElement(fLValue.getValue(), fNodeId);
		if (!(temp instanceof SymbolTableVariableElement)) {
			throw new SemanticalException(fLValue.getValue() + " is not a variable.", getLine());
		}

		// Check constants
		SymbolTableVariableElement element = (SymbolTableVariableElement) temp;
		if (element.isConstant()) {
			throw new SemanticalException("Redefinition of constant is not allowed.", getLine());
		}
		
		// Check types
		TypeSpecifier varType = new TypeSpecifier(fLValue.getResultType(currentScope));
		TypeSpecifier exprType = new TypeSpecifier(fRValue.getResultType(currentScope));
		
		// Int maybe assigned to float
		if (varType.getType(currentScope) == TypeSpecifier.FLOAT) {
			if ( (exprType.getType(currentScope) != TypeSpecifier.FLOAT) && (exprType.getType(currentScope) != TypeSpecifier.INTEGER)) {
				throw new SemanticalException("Assignment of type " + exprType.getTypeName() + " to variable of type " + varType.getTypeName() + " is not allowed.", getLine());
			}
		} else {
			if(varType.getType(currentScope) != exprType.getType(currentScope)) {
				throw new SemanticalException("Assignment of type " + exprType.getTypeName() + " to variable of type " + varType.getTypeName() + " is not allowed.", getLine());
			}
		}
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		SymbolTableElement element = currentScope.getElement(fLValue.getValue(), fNodeId);
		
		if (fRValue.isConstant(currentScope)) {
			// Load value of rValue on top of the stack
			//If the value has to be cast to another type, we have to do this in advance
			String value = "";
			if(this.fRValue.getOriginalType(currentScope) != this.fRValue.getResultType(currentScope)) {
				value = fRValue.calculateValue(currentScope).toType(this.fRValue.getResultType(currentScope));
			} else if (element.getType().getType(currentScope) != fRValue.getResultType(currentScope)) {
				value = fRValue.calculateValue(currentScope).toType(element.getType().getType(currentScope));
			} else {
				value = fRValue.calculateValue(currentScope).valueToString();
			}
			pGen.load(element.getType().getPType(currentScope), value);
		} else {
			fRValue.generatePcode(pGen, currentScope);
			if (element.getType().getType(currentScope) != fRValue.getResultType(currentScope)) {
				pGen.conv('i', 'r');
			}
		}
		
		// Store top of the stack at location of lValue
		//TODO: change depth
		pGen.str(element.getType().getPType(currentScope), currentScope.getDepth(), element.getMemoryAddress());
	}
	
}
